#!/usr/bin/perl

#------------------------------------------------------------#
# Program to bin the SS433 photometric data in all_sort.dat  #
# in various ways.                                           #
#                                             Bmort  31Jan07 #
#------------------------------------------------------------#

system("clear");

print "===================================================\n";
print "\tData binning for SS433 data\n";
print "\t(for help see binning.readme)\n";
print "===================================================\n";

# GET COMMAND ARGS FOR FILE NAMES
$numArgs = $#ARGV + 1;
if ($numArgs != 2) {
    message("ERROR: Incorect number of command line args");
    messageLine();
    usage();
    exit(1);
}

$dataFileName = $ARGV[0];
$outFile = $ARGV[1];
message("Input data file = \"$dataFileName\"");
message("Output file root= \"$outFile\"");
messageLine();

# OPEN DATA FILE AND STORE IN ARRAY OF RAW DATA
use Cwd;
$dir = getcwd(); # Where am I?
open(DATA, $dataFileName);
@lines = <DATA>; # read data file into an array
close(DATA);

# EXTRACT RAW DATA INTO USEFUL ARRAYS
#====================================
$i=0;
$lskip = 0; # fix for blank lines
@ObsTime;
@ObsMag;
foreach $line (@lines) {
    if ($line =~ /^\s+/) { print "** "; $lskip++;}
    $line =~ m/^(\d+\.\d+|\d+)\s+(\d+\.\d+|\d+)/;
    $ObsTime[$i] = $1;
    $ObsMag[$i]  = $2;
    $i++;
}


# GET SOME INFO ON THE DATA
#==========================
$dataLength = $i-$lskip;
message("Data Length (# samples) = $dataLength");
$ObsTimeRange = $ObsTime[$dataLength-1]-$ObsTime[0];
message("Obs. time range ($ObsTime[0] to $ObsTime[$dataLength-1]) = $ObsTimeRange");
messageLine();

# GET BINNING PARAMETERS (FROM STDIN - USER PROMTEDED)
#=====================================================
$BinType = &promptUser("* Binning Type (1=simple, 2=adaptive), ",1);
#simple mode parameters
if ($BinType == 1) {
    #prompt user for inputs 
    messageLine();
    title_message("Simple Binning options:");
    $binLength = &promptUser("* Enter bin length (days), ",
			     int $ObsTimeRange/5);
    $binOverlap = &promptUser("* Enter bin overlap (days), ",0);
    $binStartOffset = &promptUser("* Enter bin start offset (days), ",0);
    #print summary
    message();
    sub_message("Bin length  = $binLength");
    sub_message("Bin overlap = $binOverlap");
    sub_message("Bin start offset = $binStartOffset");
} 
#adaptive mode parameters
elsif ($BinType == 2) { # adaptive
}
else {
    message("ERROR: Unrecognised binning type!");
    exit(2);
}

if ($BinType == 1) {
#check for some simple errors in imputs
if ($binOverlap >= $binLength) {
    messageLine();
    message("ERROR: Bin overlap longer than bin length, check options!");
    exit(3);
}
}


# (SIMPLE BINNING) GET NUMBER OF BINS
#====================================
if ($BinType == 1) {
    $range = $ObsTimeRange-$binStartOffset+1;
    $overlap = $binOverlap;
    $fractNumBins = ($range-$overlap)/($binLength-$overlap);
    $intNumBins = int $fractNumBins;
    messageLine();
    message("Fractional number of bins  = $fractNumBins");   
    message("Whole number of bins       = $intNumBins");   
    if ($intNumBins < 1) {
	message();
	message("ERROR: No full length bins fit in the data set, check options!");
	exit(3);
    }
    messageLine();
}

# (SIMPLE BINNING) GENERATE IDEAL BINS (NO RELATION TO DATA)
#===========================================================
if ($BinType == 1) {
@binFileNames;  # array holding the filenames of the bin files

@binStartTimes; # arrays to hold bin edge indices and times
@binEndTimes;

$iBin = 0; # bin index
$binStartTimes[$iBin] = $ObsTime[0]+$binStartOffset;
$binEndTimes[$iBin]   = $binStartTimes[$iBin]+($binLength-1);
for ($iBin=1;$iBin<$intNumBins;$iBin++) {
    $binStartTimes[$iBin] = $binEndTimes[$iBin-1]+1-$binOverlap;
    $binEndTimes[$iBin]   = $binStartTimes[$iBin]+($binLength-1);
}
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
    $binFileNames[$iBin] = "$outFile-bin".$iBin.".dat";
}
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
    title_message("Bin-$iBin:");
    sub_message("fileName = $binFileNames[$iBin]");
    sub_message("time     = $binStartTimes[$iBin] to $binEndTimes[$iBin]");
}
#messageLine();

} # $BinType == 1 #


# (SIMPLE BINNING) FIND REAL BINS
# ===============================
if ($BinType == 1) {
# the real bin time boundary arrays
@dataBinStartTimes;
@dataBinEndTimes;  
# find the real data times closest to @BinStartTimes
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
    $tmpBinStartTimeLower = 0.0;
    $tmpBinStartTimeUpper = 0.0;
    $diffLower = 0.0;
    $diffUpper = 0.0;
    $tmpBinStartIndexLower = 0;
    $tmpBinStartIndexUpper = 0;
    for ($i=0;$i<($dataLength);$i++) {
	if ($ObsTime[$i] <= $binStartTimes[$iBin]) {
	    $tmpBinStartTimeLower  = $ObsTime[$i];
	    $tmpBinStartTimeUpper  = $ObsTime[$i+1];
	    $tmpBinStartIndexLower = $i;
	    $tmpBinStartIndexUpper = $i+1;
	}
    }
    $diffLower = $binStartTimes[$iBin]-$tmpBinStartTimeLower;
    $diffUpper = $tmpBinStartTimeUpper -$binStartTimes[$iBin];
    if ($diffLower < $diffUpper) {
	$dataBinStartTimes[$iBin] = $tmpBinStartTimeLower;
    } else {
	$dataBinStartTimes[$iBin] = $tmpBinStartTimeUpper;
    }
    #message("Start - Bin-$iBin (differences)");
    #sub_message("L: $diffLower ($tmpBinStartTimeLower $tmpBinStartIndexLower)");
    #sub_message("U: $diffUpper ($tmpBinStartTimeUpper $tmpBinStartIndexUpper)");
}
# find the read data times closest to @BinEndTimes
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
    $tmpBinEndTimeLower = 0.0;
    $tmpBinEndTimeUpper = 0.0;
    $diffLower = 0.0;
    $diffUpper = 0.0;
    $tmpBinEndIndexLower = 0;
    $tmpBinEndIndexUpper = 0;
    #print "finding closest to $binEndTimes[$iBin]...\n";
    for ($i=0;$i<($dataLength);$i++) {
	if ($ObsTime[$i] <= $binEndTimes[$iBin]) {
	    $tmpBinEndTimeLower  = $ObsTime[$i];
	    $tmpBinEndTimeUpper  = $ObsTime[$i-1];
	    $tmpBinEndIndexLower = $i;
	    $tmpBinEndIndexUpper = $i-1;
	}
    }
    $diffLower = $binEndTimes[$iBin]-$tmpBinEndTimeLower;
    $diffUpper = $tmpBinEndTimeUpper -$binEndTimes[$iBin];
    if (abs($diffLower) < abs($diffUpper)) {
	$dataBinEndTimes[$iBin] = $tmpBinEndTimeLower;
    } else {
	$dataBinEndTimes[$iBin] = $tmpBinEndTimeUpper;
    }
#    message("End - Bin-$iBin (differences)");
#    sub_message("L: $diffLower ($tmpBinStartTimeLower $tmpBinStartIndexLower)");
#    sub_message("U: $diffUpper ($tmpBinStartTimeUpper $tmpBinStartIndexUpper)");
}
messageLine();
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
    message("Bin-$iBin");
    sub_message("startTime:  $dataBinStartTimes[$iBin]");
    sub_message("endTime:    $dataBinEndTimes[$iBin]");
}

} # if $binType == 1 #
messageLine();





# (ADAPTIVE BINNING)
# ============================================
if ($BinType == 2) { 
    @timeDifference;
    # get difference array
    for ($i=0;$i<($dataLength-1);$i++) {
	$timeDifference[$i] = $ObsTime[$i+1] - $ObsTime[$i];
    }
    # get average diff
    $diffSum = 0.0;
    for ($i=0;$i<($dataLength-1);$i++) {
	$diffSum += $timeDifference[$i];
    }
    $aveDiff = $diffSum/($dataLength-1);
    @offsetDifference;
    for ($i=0;$i<($dataLength-1);$i++) {
	$offsetDifference[$i] = $timeDifference[$i] - $aveDiff;
	$offsetFactor[$i] = $timeDifference[$i]/$aveDiff;
    }
    message("average difference = $aveDiff");
    
    # OPTIONS
    # -------
    # get first guess at bin boundaries from the offsetFactor >
    # specified min offset factor (default 10)
    messageLine();
    message("The min. offset factor is the number of time the average");
    message("time difference btween samples that is seen as acceptable");
    message("for continuous measurement");
    $minOffset = &promptUser("* Enter min offset factor: ",
			     10);
    message("The min. bin length is used to determine what bins");
    message("found using the above offset criteria should be considered as");
    message("useful bins");
    $minBinLength = &promptUser("* Enter min bin length (days): ",
			     300);
    $minBinSamples = &promptUser("* Enter min bin samples (# data samples): ",
			     20);

    messageLine();
    message("Min offset factor = $minOffset");
    message("Min bin time length (days) = $minBinLength");
    message("Min data samples per bin   = $minBinSamples");
    
    
    # GUESS AT BIN EDGES (END TIMES FOR BINS ARE CALCULATED)
    # ------------------------------------------------------
    @binEdgeGuess_Times;
    @binEdgeGuess_Index;
    $kBin = 0;
    for ($i=0;$i<($dataLength-1);$i++) {
	if ($offsetFactor[$i] >= $minOffset) {
	    $binEdgeGuess_Times[$kBin] = $ObsTime[$i];
	    $binEdgeGuess_Index[$kBin] = $i;
	    ##print "$i $ObsTime[$i] $kBin $binEdgeGuess_Times[$kBin]\n";
	    $kBin++;
	}
    }
    $binEdgeGuess_Times[$kBin] = $ObsTime[$dataLength-1];
    $binEdgeGuess_Index[$kBin] = $dataLength-1;
    
    print "$binEdgeGuess_Times[$kBin]\n";
    print "$binEdgeGuess_Index[$kBin]\n";
    
    messageLine();
    $nBinEdgeGuess = $kBin;
    message("Number of bins found = $nBinEdgeGuess");
    
    # GAPS BETWEEN BINS
    # -----------------
    @binGaps;
    $gBin = 0;
    for ($g=0;$g<($nBinEdgeGuess);$g++) {
	$index1 = $binEdgeGuess_Index[$g];
	$index2 = $binEdgeGuess_Index[$g]+1;
	$binGaps[$g] = $ObsTime[$index2] - $ObsTime[$index1];
	$binEnd[$g]  = $binEdgeGuess_Times[$g] + $binGaps[$g];
	
    }
    
    # DIFFERENCE IN INDEX AND TIME FOR EACH BIN
    # ------------------------------------------
    # gets Tdiff[bin] array for the time difference between beams
    # gets Sdiff[bin] array for the index (data points) 
    # differences between beams
    $Tdiff[0] = $ObsTime[$binEdgeGuess_Index[0]] - $ObsTime[0];
    $Sdiff[0] = $binEdgeGuess_Index[0];
    $iiBin = 1;
    for ($i=1;$i<=($nBinEdgeGuess);$i++) {
	$Tdiff[$iiBin] = $binEdgeGuess_Times[$i]-$binEdgeGuess_Times[$i-1];
	$Sdiff[$iiBin] = $binEdgeGuess_Index[$i]-$binEdgeGuess_Index[$i-1];
	$iiBin++;
    }
    ###print "iiBin $iiBin\n";
    
    messageLine();
    ##print "DIFFERENCES ...\n";
    @passedBins;
    $pBin = 0;
    for ($z=0;$z<$iiBin;$z++) {
	##print "$z $binEdgeGuess_Index[$z] $Tdiff[$z] $Sdiff[$z]";
	if ( ($Tdiff[$z] >= $minBinLength) 
	     && ($Sdiff[$z] >= $minBinSamples) ) {
	    ##print "  xx\n";
	    $passedBins[$pBin] = $z;
	    $pBin++;
	} else {
	    ##print "\n";
	}
    }
    
    for ($p=0;$p<$pBin;$p++) {
	print "$passedBins[$p]\n";
    }



    for ($iBin=0;$iBin<$pBin;$iBin++) {
	$binFileNames[$iBin] = "$outFile-bin".$iBin.".dat";
    }
#    for ($iBin=0;$iBin<$pBin;$iBin++) {
#	title_message("Bin-$iBin:");
#	sub_message("fileName = $binFileNames[$iBin]");
#	sub_message("time     = $binStartTimes[$iBin] to $binEndTimes[$iBin]");
#    }

} # if ($BinType == 2) # END OF ADAPTIVE BINNING # 





# (ADAPTIVE BINNING) OUTPUT TO FILE
# =================================


if ($BinType == 2) {     

    # DIFFERENCES (BETWEEN INDIVIDAL DATA POINTS)
    # file containing the differences between data points
    # this is expressed as absolute time difference, offset from the
    # average differnce and the offset factor from the average (ie.
    # the time difference / mean time difference)
    # <DATA POINT INDEX> <TIME DIFFERENCE> <TIME OFFSET> <OFFSET FACTOR>
    title_message("writing $outFile.diffs");
    open(OUT_DIFFS,">$outFile.diffs");
    for ($i=0;$i<($dataLength-1);$i++) {
	print OUT_DIFFS "$i $ObsTime[$i]\t$timeDifference[$i]\t";
	print OUT_DIFFS "$offsetDifference[$i] $offsetFactor[$i]\n";
    }
    close(OUT_DIFFS);
   
    # EDGE GUESSES <<< *.binEdgeGuess >>>
    # ---------------------------------------------------------
    # output file containing the first guess of bin edges based
    # on the input parameters for adaptive binning
    # <BIN NO> <INDEX> <TIME> <15 (dummy value!)> <TIME DIFF> ...
    # ... <GAP TIME> <BIN END TIME> <INDEX DIFF>
    title_message("writing $outFile.binEdgeGuess");
    open(OUT_EDGE,">$outFile.binEdgeGuess");
    for ($i=0;$i<=($nBinEdgeGuess);$i++) {
	print OUT_EDGE "$i\t$binEdgeGuess_Index[$i]\t";
	print OUT_EDGE "$binEdgeGuess_Times[$i]\t15\t";
	printf OUT_EDGE ("%.1f\t%.1f\t",$Tdiff[$i],$binGaps[$i]);
	print OUT_EDGE " $binEnd[$i]\t$Sdiff[$i]\n";
    }
    close(OUT_EDGE);
    
    # BIN DATA FILES
    # this is where the actual bin data files are written out
    # <TIME OF OBSERVATION> <DATA MAGNITUDE> <DATA INDEX>
    messageLine();
    for ($iBin=0;$iBin<$pBin;$iBin++) {
	title_message("Writing $binFileNames[$iBin]");
	open(OUT_BIN, ">$binFileNames[$iBin]");
	#print STDOUT "$passedBins[$iBin]\n";
	for ($i=0;$i<($dataLength);$i++) {
	    if($ObsTime[$i] > $binEdgeGuess_Times[$passedBins[$iBin]-1] &&
	       $ObsTime[$i] <= $binEdgeGuess_Times[$passedBins[$iBin]]) {
		print OUT_BIN "$ObsTime[$i]  $ObsMag[$i] $i\n";
	    }
	}
    #message("done!");
	close(OUT_BIN);
    }
    messageLine();
} # $BinType == 2 # END OF ADAPTIVE BINNING OUTPUT #



# OUTPUT TO FILE
# ============== 

# write the input data back to file (for checking)
title_message("Writing $outFile.indata");
open(OUT_INDATA, ">$outFile.indata");
for ($i=0;$i<($dataLength);$i++) {
    print OUT_INDATA "$ObsTime[$i]\t $ObsMag[$i] \t $i\n";
}
close(OUT_INDATA);

# write the info file on data and binning
title_message("Writing $outFile.info");
open(OUT_INFO, ">$outFile.info");
print OUT_INFO "RUNDIR      =        $dir\n";
print OUT_INFO "OUTFILE     =        $dataFileName\n";
print OUT_INFO "DATAFILE    =        $outFile\n";
print OUT_INFO "DATALENGTH  =        $dataLength\n";
print OUT_INFO "OBS_T_RANGE =        $ObsTimeRange\n";
if ($BinType == 1) {
print OUT_INFO "BINTYPE     =        Simple binning\n";
print OUT_INFO "LENGTH      =        $binLength\n";
print OUT_INFO "OVERLAP     =        $binOverlap\n";
print OUT_INFO "OFFSET      =        $binStartOffset\n";
print OUT_INFO "FRACBINS    =        $fractNumBins\n";
print OUT_INFO "INTBINS     =        $intNumBins\n";
print OUT_INFO "TOT_OVERLAP =        $overlaps\n";
}
if ($BinType == 2) {
print OUT_INFO "BINTYPE     =        Adaptive\n";
print OUT_INFO "NUM BINS    =                \n";
}
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
print OUT_INFO "#BIN        =        Bin-$iBin:\n";
print OUT_INFO "FILENAME    =        $binFileNames[$iBin]\n";
print OUT_INFO "START_TIME  =        $dataBinStartTimes[$iBin]\n";
print OUT_INFO "END_TIME    =        $dataBinEndTimes[$iBin]\n";
}
close(OUT_INFO);

# write a summary file with the input data and bin start/end flags
if ($BinType == 1) {
$iBin=0;
title_message("Writing $outFile.summary");
open(OUT_SUMMARY, ">$outFile.summary");
for ($i=0;$i<($dataLength);$i++) {
    if ($ObsTime[$i] == $dataBinStartTimes[$iBin]) {
	print OUT_SUMMARY "$ObsTime[$i]\t $ObsMag[$i]   <== START ($iBin)\n";
    }
    if ($ObsTime[$i] == $dataBinEndTimes[$iBin]) {
	print OUT_SUMMARY "$ObsTime[$i]\t $ObsMag[$i]   <== END ($iBin)\n";
	$iBin++;
    }
    else {
	print OUT_SUMMARY "$ObsTime[$i]\t $ObsMag[$i]\n";
    }
}
close(OUT_SUMMARY);
messageLine();
}

# write the bin data
for ($iBin=0;$iBin<$intNumBins;$iBin++) {
    title_message("Writing $binFileNames[$iBin]");
    open(OUT_BIN, ">$binFileNames[$iBin]");
    for ($i=0;$i<($dataLength);$i++) {
	if($ObsTime[$i] >= $dataBinStartTimes[$iBin] &&
	   $ObsTime[$i] <= $dataBinEndTimes[$iBin]) {
		print OUT_BIN "$ObsTime[$i]  $ObsMag[$i]\n";
	}
    }
    #message("done!");
    close(OUT_BIN);
}
messageLine();














#################################################

sub usage()
{
    print "-\n";
    print "- USAGE: []\$ perl dataBinning.pl [DATAFILENAME] [OUTNAME_ROOT]\n";
    print "-\n";
}

sub message {
    my $messageString = shift;
    print "- $messageString\n";
}

sub messageLine {
    print "---------------------------------------------------\n";
}


sub sub_message {
    my $messageString = shift;
    print "-   $messageString\n";
}

sub title_message {
    my $messageString = shift;
    print "= $messageString\n";
}


sub promptUser {
    
    #-------------------------------------------------------------------#
    #  two possible input arguments - $promptString, and $defaultValue  #
    #  make the input arguments local variables.                        #
    #-------------------------------------------------------------------#
    
    local($promptString,$defaultValue) = @_;
    
    #-------------------------------------------------------------------#
    #  if there is a default value, use the first print statement; if   #
    #  no default is provided, print the second string.                 #
    #-------------------------------------------------------------------#
    
    
    if ($defaultValue || $defaultValue == 0) {
	print $promptString, "[", $defaultValue, "]: ";
    } else {
	print $promptString, ": ";
    }

    $| = 1;               # force a flush after our print
    $_ = <STDIN>;         # get the input from STDIN (presumably the keyboard)


    #------------------------------------------------------------------#
    # remove the newline character from the end of the input the user  #
    # gave us.                                                         #
    #------------------------------------------------------------------#

    chomp;

    #-----------------------------------------------------------------#
    #  if we had a $default value, and the user gave us input, then   #
    #  return the input; if we had a default, and they gave us no     #
    #  no input, return the $defaultValue.                            #
    #                                                                 # 
    #  if we did not have a default value, then just return whatever  #
    #  the user gave us.  if they just hit the <enter> key,           #
    #  the calling routine will have to deal with that.               #
    #-----------------------------------------------------------------#

    if ("$defaultValue" || $defaultValue == 0) {
	return $_ ? $_ : $defaultValue;    # return $_ if it has a value
    } else {
	return $_;
    }
}


